using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.ComponentModel;
using System.Globalization;
using C4F.DevKit.WebServices.Properties;
using C4F.DevKit.WebServices.Datasets;

namespace C4F.DevKit.WebServices
{

    /// <summary>
    /// Exposes search functionality on Flickr.com
    /// </summary>
    [ToolboxItem(true)]
    public class Flickr : C4FWebServicesBase
    {
        #region Private members
        /// <summary>
        /// Base url
        /// </summary>
        private string baseUrl = Resources.FlickrBaseUrl;
        /// <summary>
        /// Application key
        /// </summary>
        private string applicationKey = string.Empty;

        #endregion

        #region Accessors
        /// <summary>
        /// Access Key required to use the Api
        /// </summary>
        [Category("Flickr")]
        [Description("Access Key required to use the Api")]
        public string ApplicationKey
        {
            get { return this.applicationKey == null ? string.Empty : this.applicationKey.Trim(); }
            set { this.applicationKey = value.Trim(); }
        }

        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new object of Flickr class
        /// </summary>
        public Flickr()
        {
        }
        /// <summary>
        /// Initializes a new object of Flickr class with an application key
        /// </summary>
        /// <param name="applicationKey">Application key</param>
        public Flickr(string applicationKey)
        {
            this.applicationKey = applicationKey;
        }

        #endregion

        #region Public functions

        /// <summary>
        /// Searches for a list of photos matching the criteria
        /// </summary>
        /// <param name="userId">Id of the user who's photo to search</param>
        /// <param name="tags">List of tags</param>
        /// <param name="tagMode">tag mode (Either 'any' for an OR combination of tags or 'all' for an AND combination)</param>
        /// <param name="text">A free text search</param>
        /// <param name="minUploadDate">Minimum upload date</param>
        /// <param name="maxUploadDate">Maximum upload date</param>
        /// <param name="minTakenDate">Minimum taken date</param>
        /// <param name="maxTakenDate">Maximum taken date</param>
        /// <param name="license">The license id for photos</param>
        /// <param name="sort">The order in which to sort returned photos</param>
        /// <param name="privacyFilter">Return photos only matching a certain privacy level</param>
        /// <param name="bbox">A list of 4 values defining the Bounding Box of the area that will be searched</param>
        /// <param name="accuracy">Recorded accuracy level of the location information</param>
        /// <param name="extras">A list of extra information to fetch for each returned record</param>
        /// <param name="numberOfPagesForResult">The page of results to return</param>
        /// <param name="resultsPerPage">Results per page</param>
        /// <returns>A list of photos matching the criteria</returns>
        /// <exception cref="InvalidOperationException">The Application Key is not set</exception>
        public List<PhotoData> Search(string userId, string tags, string tagMode,
            string text, string minUploadDate, string maxUploadDate, string minTakenDate,
            string maxTakenDate, string license, string sort, int? privacyFilter,
            string bbox, int? accuracy, string extras, int? numberOfPagesForResult,
            int? resultsPerPage)
        {
            if (String.IsNullOrEmpty(this.ApplicationKey))
                throw new InvalidOperationException(Resources.ErrorApplicationKeyNotSet);

            string requestUrl = this.GetQueryString(userId, tags, tagMode,
                text, minUploadDate, maxUploadDate, minTakenDate, maxTakenDate,
                license, sort, privacyFilter, bbox, accuracy, extras,
                numberOfPagesForResult, resultsPerPage);

            // Get Flickr responses
            string responseData = base.GetQueryResponse(requestUrl);

            // Parse the data and return results
            List<PhotoData> photoDataList = ParseResponseXml(responseData);

            // Fill up the photo url using flicke.photo.getinfo methos.
            FillPhotoUrl(photoDataList);

            return photoDataList;
        }

        /// <summary>
        /// Searches for a list of photos matching the criteria
        /// </summary>
        /// <param name="text">A free text search</param>
        /// <returns>A list of photos matching the criteria</returns>
        /// <exception cref="InvalidOperationException">The Application Key is not set</exception>
        /// <exception cref="ArgumentException">The text is not specified</exception>
        public List<PhotoData> Search(string text)
        {
            if (String.IsNullOrEmpty(this.ApplicationKey))
                throw new InvalidOperationException(Resources.ErrorApplicationKeyNotSet);
            if (String.IsNullOrEmpty(text))
                throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "text"));

            string requestUrl = this.GetQueryString(null, null, null, text, null,
                    null, null, null, null, null, null, null, null, null, null, null);

            // Get Flickr responses
            string responseData = base.GetQueryResponse(requestUrl);

            // Parse the data and return results
            List<PhotoData> photoDataList = ParseResponseXml(responseData);

            // Fill up the photo url using flicke.photo.getinfo methos.
            FillPhotoUrl(photoDataList);

            return photoDataList;
        }

        /// <summary>
        /// Searches for a list of photos matching the criteria
        /// </summary>
        /// <param name="userId">Id of the user who's photo to search</param>
        /// <param name="tags">List of tags</param>
        /// <returns>A list of photos matching the criteria</returns>
        /// <exception cref="InvalidOperationException">The Application Key is not set</exception>
        /// <exception cref="ArgumentException">The userId is not specified (OR) the tags are not specified</exception>
        public List<PhotoData> Search(string userId, string tags)
        {
            if (String.IsNullOrEmpty(this.ApplicationKey))
                throw new InvalidOperationException(Resources.ErrorApplicationKeyNotSet);

            if (String.IsNullOrEmpty(userId))
                throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "userId"));

            if (String.IsNullOrEmpty(tags))
                throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "tag"));

            string requestUrl = this.GetQueryString(userId, tags, null, null, null,
                    null, null, null, null, null, null, null, null, null, null, null);

            // Get Flickr responses
            string responseData = base.GetQueryResponse(requestUrl);

            // Parse the data and return results
            List<PhotoData> photoDataList = ParseResponseXml(responseData);

            // Fill up the photo url using flicke.photo.getinfo methos.
            FillPhotoUrl(photoDataList);

            return photoDataList;
        }

        /// <summary>
        /// Gets a list of photos of the specified user
        /// </summary>
        /// <param name="userId">Id of the user who's photo to search</param>
        /// <returns>A list of photos matching the criteria</returns>
        /// <exception cref="InvalidOperationException">The Application Key is not set</exception>
        /// <exception cref="ArgumentException">The userId is not specified</exception>
        public List<PhotoData> SearchByUserId(string userId)
        {
            if (String.IsNullOrEmpty(this.ApplicationKey))
                throw new InvalidOperationException(Resources.ErrorApplicationKeyNotSet);

            if (String.IsNullOrEmpty(userId))
                throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "userId"));

            string requestUrl = this.GetQueryString(userId, null, null, null, null,
                    null, null, null, null, null, null, null, null, null, null, null);

            // Get Flickr responses
            string responseData = base.GetQueryResponse(requestUrl);

            // Parse the data and return results
            List<PhotoData> photoDataList = ParseResponseXml(responseData);

            // Fill up the photo url using flicke.photo.getinfo methos.
            FillPhotoUrl(photoDataList);

            return photoDataList;
        }

        /// <summary>
        /// Searches for a list of photos matching the criteria and returns the result in a dataset
        /// </summary>
        /// <param name="userId">Id of the user who's photo to search</param>
        /// <param name="tags">List of tags</param>
        /// <param name="tagMode">tag mode (Either 'any' for an OR combination of tags or 'all' for an AND combination)</param>
        /// <param name="text">A free text search</param>
        /// <param name="minUploadDate">Minimum upload date</param>
        /// <param name="maxUploadDate">Maximum upload date</param>
        /// <param name="minTakenDate">Minimum taken date</param>
        /// <param name="maxTakenDate">Maximum taken date</param>
        /// <param name="license">The license id for photos</param>
        /// <param name="sort">The order in which to sort returned photos</param>
        /// <param name="privacyFilter">Return photos only matching a certain privacy level</param>
        /// <param name="bbox">A list of 4 values defining the Bounding Box of the area that will be searched</param>
        /// <param name="accuracy">Recorded accuracy level of the location information</param>
        /// <param name="extras">A list of extra information to fetch for each returned record</param>
        /// <param name="numberOfPagesForResult">The page of results to return</param>
        /// <param name="resultsPerPage">Results per page</param>
        /// <returns>Dataset containing list of photos matching the criteria</returns>
        /// <exception cref="InvalidOperationException">The Application Key is not set</exception>
        public DS_Flickr Search_DS(string userId, string tags, string tagMode,
            string text, string minUploadDate, string maxUploadDate, string minTakenDate,
            string maxTakenDate, string license, string sort, int? privacyFilter,
            string bbox, int? accuracy, string extras, int? numberOfPagesForResult,
            int? resultsPerPage)
        {
            List<PhotoData> photos = Search(userId, tags, tagMode, text, minUploadDate, maxUploadDate,
                                minTakenDate, maxTakenDate, license, sort, privacyFilter, bbox, accuracy, extras,
                                numberOfPagesForResult, resultsPerPage);
            DS_Flickr dataset = new DS_Flickr();
            foreach (PhotoData photo in photos)
            {
                DS_Flickr.PhotoDetailsRow photoDetailsRow = dataset.PhotoDetails.AddPhotoDetailsRow(photo.PhotoDetails.Owner, photo.PhotoDetails.Secret, photo.PhotoDetails.Server,
                    photo.PhotoDetails.Farm, photo.PhotoDetails.IsPublic, photo.PhotoDetails.IsFriend,
                    photo.PhotoDetails.IsFamily);

                dataset.PhotoData.AddPhotoDataRow(photo.Id, photo.Title, photo.Url, photoDetailsRow);
            }
            return dataset;
        }

        /// <summary>
        /// Searches for a list of photos matching the criteria and returns the result in a dataset
        /// </summary>
        /// <param name="userId">Id of the user who's photo to search</param>
        /// <param name="tags">List of tags</param>
        /// <returns>Dataset containing list of photos matching the criteria</returns>
        /// <exception cref="InvalidOperationException">The Application Key is not set</exception>
        /// <exception cref="ArgumentException">The userId is not specified (OR) the tags are not specified</exception>
        public DS_Flickr Search_DS(string userId, string tags)
        {
            List<PhotoData> photos = Search(userId, tags);
            DS_Flickr dataset = new DS_Flickr();
            foreach (PhotoData photo in photos)
            {
                DS_Flickr.PhotoDetailsRow photoDetailsRow = dataset.PhotoDetails.AddPhotoDetailsRow(photo.PhotoDetails.Owner, photo.PhotoDetails.Secret, photo.PhotoDetails.Server,
                    photo.PhotoDetails.Farm, photo.PhotoDetails.IsPublic, photo.PhotoDetails.IsFriend,
                    photo.PhotoDetails.IsFamily);

                dataset.PhotoData.AddPhotoDataRow(photo.Id, photo.Title, photo.Url, photoDetailsRow);
            }
            return dataset;
        }

        /// <summary>
        /// Gets a list of photos of the specified user and returns the result in a dataset
        /// </summary>
        /// <param name="userId">Id of the user who's photo to search</param>
        /// <returns>Dataset containing list of photos of the specified user</returns>
        /// <exception cref="InvalidOperationException">The Application Key is not set</exception>
        /// <exception cref="ArgumentException">The userId is not specified</exception>
        public DS_Flickr SearchByUserId_DS(string userId)
        {
            List<PhotoData> photos = SearchByUserId(userId);
            DS_Flickr dataset = new DS_Flickr();
            foreach (PhotoData photo in photos)
            {
                DS_Flickr.PhotoDetailsRow photoDetailsRow = dataset.PhotoDetails.AddPhotoDetailsRow(photo.PhotoDetails.Owner, photo.PhotoDetails.Secret, photo.PhotoDetails.Server,
                    photo.PhotoDetails.Farm, photo.PhotoDetails.IsPublic, photo.PhotoDetails.IsFriend,
                    photo.PhotoDetails.IsFamily);

                dataset.PhotoData.AddPhotoDataRow(photo.Id, photo.Title, photo.Url, photoDetailsRow);
            }
            return dataset;
        }

        /// <summary>
        /// Searches for a list of photos matching the criteria and returns the result in a dataset
        /// </summary>
        /// <param name="text">A free text search</param>
        /// <returns>Dataset containing list of photos matching the criteria</returns>
        /// <exception cref="InvalidOperationException">The Application Key is not set</exception>
        /// <exception cref="ArgumentException">The text is not specified</exception>
        public DS_Flickr Search_DS(string text)
        {
            List<PhotoData> photos = Search(text);
            DS_Flickr dataset = new DS_Flickr();
            foreach (PhotoData photo in photos)
            {
                DS_Flickr.PhotoDetailsRow photoDetailsRow = dataset.PhotoDetails.AddPhotoDetailsRow(photo.PhotoDetails.Owner, photo.PhotoDetails.Secret, photo.PhotoDetails.Server,
                    photo.PhotoDetails.Farm, photo.PhotoDetails.IsPublic, photo.PhotoDetails.IsFriend,
                    photo.PhotoDetails.IsFamily);

                dataset.PhotoData.AddPhotoDataRow(photo.Id, photo.Title, photo.Url, photoDetailsRow);
            }
            return dataset;
        }
        #endregion

        #region Private functions
        /// <summary>
        /// Prepare the query string based on parameters
        /// </summary>
        /// <param name="userId">Id of the user who's photo to search</param>
        /// <param name="tags">List of tags</param>
        /// <param name="tagMode">tag mode (Either 'any' for an OR combination of tags or 'all' for an AND combination)</param>
        /// <param name="text">A free text search</param>
        /// <param name="minUploadDate">Minimum upload date</param>
        /// <param name="maxUploadDate">Maximum upload date</param>
        /// <param name="minTakenDate">Minimum taken date</param>
        /// <param name="maxTakenDate">Maximum taken date</param>
        /// <param name="license">The license id for photos</param>
        /// <param name="sort">The order in which to sort returned photos</param>
        /// <param name="privacyFilter">Return photos only matching a certain privacy level</param>
        /// <param name="bbox">A list of 4 values defining the Bounding Box of the area that will be searched</param>
        /// <param name="accuracy">Recorded accuracy level of the location information</param>
        /// <param name="extras">A list of extra information to fetch for each returned record</param>
        /// <param name="numberOfPagesForResult">The page of results to return</param>
        /// <param name="resultsPerPage">Results per page</param>
        /// <returns>Request Url</returns>
        private string GetQueryString(string userId, string tags, string tagMode,
            string text, string minUploadDate, string maxUploadDate, string minTakenDate,
            string maxTakenDate, string license, string sort, int? privacyFilter,
            string bbox, int? accuracy, string extras, int? numberOfPagesForResult,
            int? resultsPerPage)
        {
            bool validParameterFound = false;
            StringBuilder queryBuilder = new StringBuilder();

            // Add application key
            queryBuilder.Append("&api_key=");
            queryBuilder.Append(this.ApplicationKey);

            // Add userId
            if (!string.IsNullOrEmpty(userId) && userId.Trim().Length > 0)
            {
                queryBuilder.Append("&user_id=");
                queryBuilder.Append(userId);
                validParameterFound = true;
            }

            // Add tags to search
            if (!string.IsNullOrEmpty(tags) && tags.Trim().Length > 0)
            {
                queryBuilder.Append("&tags=");
                queryBuilder.Append(tags);
                validParameterFound = true;
            }

            // Add tag mode
            if (!string.IsNullOrEmpty(tagMode) && tagMode.Trim().Length > 0)
            {
                queryBuilder.Append("&tag_mode=");
                queryBuilder.Append(tagMode);
                validParameterFound = true;
            }

            // Add text 
            if (!string.IsNullOrEmpty(text) && text.Trim().Length > 0)
            {
                queryBuilder.Append("&text=");
                queryBuilder.Append(text);
                validParameterFound = true;
            }

            // Add min upload date
            if (!string.IsNullOrEmpty(minUploadDate) && minUploadDate.Trim().Length > 0)
            {
                queryBuilder.Append("&min_upload_date=");
                queryBuilder.Append(minUploadDate);
                validParameterFound = true;
            }

            // Add max upload date
            if (!string.IsNullOrEmpty(maxUploadDate) && maxUploadDate.Trim().Length > 0)
            {
                queryBuilder.Append("&max_upload_date=");
                queryBuilder.Append(maxUploadDate);
                validParameterFound = true;
            }

            // Add min taken date
            if (!string.IsNullOrEmpty(minTakenDate) && minTakenDate.Trim().Length > 0)
            {
                queryBuilder.Append("&min_taken_date=");
                queryBuilder.Append(minTakenDate);
                validParameterFound = true;
            }

            // Add max taken date
            if (!string.IsNullOrEmpty(maxTakenDate) && maxTakenDate.Trim().Length > 0)
            {
                queryBuilder.Append("&max_taken_date=");
                queryBuilder.Append(maxTakenDate);
                validParameterFound = true;
            }

            // Add license 
            if (!string.IsNullOrEmpty(license) && license.Trim().Length > 0)
            {
                queryBuilder.Append("&license=");
                queryBuilder.Append(license);
                validParameterFound = true;
            }

            // Add sort  
            if (!string.IsNullOrEmpty(sort) && sort.Trim().Length > 0)
            {
                queryBuilder.Append("&sort=");
                queryBuilder.Append(sort);
                validParameterFound = true;
            }

            // Add privacy filter   
            if (privacyFilter.HasValue)
            {
                queryBuilder.Append("&privacy_filter=");
                queryBuilder.Append(privacyFilter.Value.ToString(CultureInfo.InvariantCulture));
                validParameterFound = true;
            }

            // Add bbox 
            if (!string.IsNullOrEmpty(bbox) && bbox.Trim().Length > 0)
            {
                queryBuilder.Append("&bbox=");
                queryBuilder.Append(bbox);
                validParameterFound = true;
            }

            // Add accuracy 
            if (accuracy.HasValue)
            {
                queryBuilder.Append("&accuracy=");
                queryBuilder.Append(accuracy.Value.ToString(CultureInfo.InvariantCulture));
                validParameterFound = true;
            }

            // Add extras 
            if (!string.IsNullOrEmpty(extras) && extras.Trim().Length > 0)
            {
                queryBuilder.Append("&extras=");
                queryBuilder.Append(extras);
                validParameterFound = true;
            }

            // Add numberOfPagesForResult
            if (numberOfPagesForResult.HasValue)
            {
                queryBuilder.Append("&page=");
                queryBuilder.Append(numberOfPagesForResult.Value);
                validParameterFound = true;
            }

            // Add resultsPerPage
            if (resultsPerPage.HasValue)
            {
                queryBuilder.Append("&per_page=");
                queryBuilder.Append(resultsPerPage);
                validParameterFound = true;
            }

            string methodName = null;

            if (validParameterFound)
                methodName = "?method=flickr.photos.search";
            else
                methodName = "?method=flickr.photos.getrecent";

            return this.baseUrl + methodName + queryBuilder.ToString();
        }

        /// <summary>
        /// Parses the response data and extracts the photo items
        /// </summary>
        /// <param name="responseData">Response data</param>
        /// <returns>Photo item list</returns>
        private static List<PhotoData> ParseResponseXml(string responseData)
        {
            XmlDocument xmlDocument = new XmlDocument();
            List<PhotoData> photoDataList = null;

            xmlDocument.LoadXml(responseData);
            // Retrieve result nodes
            XmlNodeList photoNodes =
                xmlDocument.DocumentElement.SelectNodes("photos/photo");

            if (photoNodes == null || photoNodes.Count == 0)
                return photoDataList;

            photoDataList = new List<PhotoData>(photoNodes.Count);

            for (int i = 0; i < photoNodes.Count; i++)
            {
                XmlNode photoNode = photoNodes[i];
                PhotoData photoData = new PhotoData();
                photoData.PhotoDetails = new PhotoDetails();
                photoDataList.Add(photoData);

                // Populate PhotoData object
                if (photoNode.Attributes != null && photoNode.Attributes.Count > 0)
                {
                    photoData.Id = photoNode.Attributes["id"] == null ? null :
                        photoNode.Attributes["id"].Value;

                    photoData.PhotoDetails.Owner = photoNode.Attributes["owner"] == null ? null :
                        photoNode.Attributes["owner"].Value;

                    photoData.PhotoDetails.Secret = photoNode.Attributes["secret"] == null ? null :
                        photoNode.Attributes["secret"].Value;

                    photoData.PhotoDetails.Server = photoNode.Attributes["server"] == null ? 0 :
                         Int32.Parse(photoNode.Attributes["server"].Value, CultureInfo.InvariantCulture);

                    photoData.PhotoDetails.Farm = photoNode.Attributes["farm"] == null ? 0 :
                          Int32.Parse(photoNode.Attributes["farm"].Value, CultureInfo.InvariantCulture);

                    photoData.Title = photoNode.Attributes["title"] == null ? null :
                        photoNode.Attributes["title"].Value;

                    photoData.PhotoDetails.IsPublic = photoNode.Attributes["ispublic"] == null ? 0 :
                        Int32.Parse(photoNode.Attributes["ispublic"].Value, CultureInfo.InvariantCulture);

                    photoData.PhotoDetails.IsFriend = photoNode.Attributes["isfriend"] == null ? 0 :
                        Int32.Parse(photoNode.Attributes["isfriend"].Value, CultureInfo.InvariantCulture);

                    photoData.PhotoDetails.IsFamily = photoNode.Attributes["isfamily"] == null ? 0 :
                        Int32.Parse(photoNode.Attributes["isfamily"].Value, CultureInfo.InvariantCulture);
                }
            }
            return photoDataList;
        }
        /// <summary>
        /// Fill the photo Url field in the photo item object
        /// </summary>
        /// <param name="photoDataList">Photo item list</param>
        private void FillPhotoUrl(List<PhotoData> photoDataList)
        {
            if (photoDataList == null || photoDataList.Count <= 0)
                return;

            for (int i = 0; i < photoDataList.Count; i++)
                photoDataList[i].Url = GetPhotoUrl(photoDataList[i].Id);
        }
        /// <summary>
        /// Gets the photo Url of the photo item
        /// </summary>
        /// <param name="photoId">Photo Id of the photo item whose Url is requested </param>
        /// <returns>Photo Url</returns>
        private string GetPhotoUrl(string photoId)
        {
            if (string.IsNullOrEmpty(photoId))
                return null;

            string requestUrl = this.baseUrl + "?method=flickr.photos.getInfo" +
                "&api_key=" + this.ApplicationKey +
                "&photo_id=" + photoId;

            string responseData = base.GetQueryResponse(requestUrl);

            return GetUrlFromXml(responseData);
        }
        /// <summary>
        /// Parses the response data and extracts the photo Url
        /// </summary>
        /// <param name="responseData">Response data</param>
        /// <returns>Photo Url</returns>
        private static string GetUrlFromXml(string responseData)
        {
            // parse xml to get url of photo.
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(responseData);

            // Retrieve result nodes
            XmlNode urlsNode =
                xmlDocument.DocumentElement.SelectSingleNode("photo/urls");

            if (urlsNode == null)
                return null;

            XmlNodeList urlNodes = urlsNode.SelectNodes("url");
            if (urlNodes == null || urlNodes.Count <= 0)
                return null;

            for (int i = 0; i < urlNodes.Count; i++)
            {
                if (urlNodes[i].Attributes == null ||
                    urlNodes[i].Attributes.Count <= 0 ||
                    urlNodes[i].Attributes["type"] == null)
                    continue;

                if (string.Compare(urlNodes[i].Attributes["type"].Value,
                    "photopage", false, CultureInfo.InvariantCulture) != 0)
                    continue;

                return urlNodes[i].InnerText;
            }
            return null;
        }
        #endregion
    }

    /// <summary>
    /// Represents a photo item Flickr.com
    /// </summary>
    public class PhotoData
    {
        #region Private Members
        /// <summary>
        /// Id of the photo
        /// </summary>
        private string id;
        /// <summary>
        /// Title of the photo
        /// </summary>
        private string title;
        /// <summary>
        /// URL to the photo
        /// </summary>
        private string url;
        /// <summary>
        /// Photo details
        /// </summary>
        private PhotoDetails photoDetails;
        #endregion

        #region Public Properties
        /// <summary>
        /// Id of the photo
        /// </summary>
        public string Id
        {
            get { return this.id; }
            set { this.id = value; }
        }
        /// <summary>
        /// Title of the photo
        /// </summary>
        public string Title
        {
            get { return this.title; }
            set { this.title = value; }
        }
        /// <summary>
        /// URL to the photo
        /// </summary>
        public string Url
        {
            get { return this.url; }
            set { this.url = value; }
        }
        /// <summary>
        /// Photo details
        /// </summary>
        public PhotoDetails PhotoDetails
        {
            get { return this.photoDetails; }
            set { this.photoDetails = value; }
        }
        #endregion
    }

    /// <summary>
    /// This class represents the photo details
    /// </summary>
    public class PhotoDetails
    {
        #region Private Members
        /// <summary>
        /// Owner of the photo
        /// </summary>
        private string owner;
        /// <summary>
        /// Secret word
        /// </summary>
        private string secret;
        /// <summary>
        /// Server id
        /// </summary>
        private int server;
        /// <summary>
        /// Photo farm
        /// </summary>
        private int farm;
        /// <summary>
        /// 1 if photo is public
        /// </summary>
        private int isPublic;
        /// <summary>
        /// 1 if is a friend
        /// </summary>
        private int isFriend;
        /// <summary>
        /// 1 if is of family
        /// </summary>
        private int isFamily;
        #endregion

        #region Public Properties
        /// <summary>
        /// Owner of the photo
        /// </summary>
        public string Owner
        {
            get { return this.owner; }
            set { this.owner = value; }
        }
        /// <summary>
        /// Secret word
        /// </summary>
        public string Secret
        {
            get { return this.secret; }
            set { this.secret = value; }
        }
        /// <summary>
        /// Server id
        /// </summary>
        public int Server
        {
            get { return this.server; }
            set { this.server = value; }
        }
        /// <summary>
        /// Photo farm
        /// </summary>
        public int Farm
        {
            get { return this.farm; }
            set { this.farm = value; }
        }
        /// <summary>
        /// 1 if photo is public
        /// </summary>
        public int IsPublic
        {
            get { return this.isPublic; }
            set { this.isPublic = value; }
        }
        /// <summary>
        /// 1 if is a friend
        /// </summary>
        public int IsFriend
        {
            get { return this.isFriend; }
            set { this.isFriend = value; }
        }
        /// <summary>
        /// 1 if is of family
        /// </summary>
        public int IsFamily
        {
            get { return this.isFamily; }
            set { this.isFamily = value; }
        }
        #endregion
    }
}